FSD에 대해 알아보는 이유
나는 카카오, 현대, 대한항공 등 많은 대기업들이 사용하고 있는 채용솔루션을 개발하고 있다.
이번에 현대 전 계열사의 계약을 따내기 위해 10년 된 지원서 서비스를 리뉴얼했다.
채용 솔루션의 핵심 서비스로 굉장히 규모가 크기 때문에 FSD 구조를 처음부터 적용하려는 노력을 했었다. 하지만, 프로젝트 초반부터 도입하기에는 생산성이 떨어져서 일반적으로 사용했던 구조로 돌아갔었다.
오늘은 FSD구조에 대해 더 자세히 알아보고 왜 도입에 실패 했었는지 다음에 어떤 상황에 적용하면 좋을지 테오님의 “프론트엔드 개발자 관점으로 바라보는 관심사의 분리와 좋은 폴더 구조(feat.FSD)”로 인사이트를 얻어보고자 한다.
테오님의 포스팅을 위에서부터 읽어보면 프론트엔드 분야에서 관심사의 분리가 어떤 식으로 발전하게 되었는지 많은 그림과 함께 흥미롭게 설명해주신다.
위에 내용 대부분은 우리가 어디서 들어봤거나 자연스럽게 알고 있는 내용이지만 쉽게 풀어서 설명해주셔서 재밌게 읽을 수 있다.
FSD에 대한 핵심은 “어떻게 적용을 할까? 프론트엔드의 핵심 관심사와 폴더 구조의 진화”부터 읽을 수 있었는데, 이론보다 실제 개발할 때를 가정하고 어떤 순서로 구조가 발전해서 결국 FSD 구조가 되는지에 대해 알기쉽게 설명해주셨다.
핵심
핵심만 정리를 해보면 다음과 같다.
-
초창기에는 일반적인 폴더구조와 크게 다르지 않다.
pages
와shared
를 나누고shared
에 공통으로 사용하는api
와ui
를 모은다. -
프로젝트가 조금씩 커지고 화면이 복잡해지기 시작할 때 root에 있는
pages
와shared
는 유지된다. 대신pages
안에서 각 페이지별로 독립된api
,ui
,model
을 관리하기 시작한다.이유는 일부 공통로직을 제외하고는 독자적으로 쓰이는 것들이 많기 때문이다.
실제로 여러 프로젝트를 경험해 본 결과, 초창기 프로젝트 구조(pages와 shared)가 오래 갔을 경우에 shared 폴더가 비대해지게 되면서 어떤 것이 있는지 점차 잊혀지기가 쉬웠고, 자칫하면 이미 있음에도 반복적인 작업을 하거나 매번 shared에 뭐가 있는지 확인하는 비용이 너무 컸다. 이 타이밍이 page별로 분리를 해야 되는 좋은 타이밍인 것 같다.
-
프로젝트 마지막에 다가올 때
더 이상 화면을 만드는 일보다 API와 비즈니스 로직 처리가 주된 작업이 된다. 대화의 핵심이 화면단 보다는 데이터를 중심으로 얘기를 하는 상황이 대부분이다.
이 때 발생하는 문제는 화면과 도메인이 강하게 결합된 복합 컴포넌트 형태라는 것이다. 디버깅이 점차 어려워지기 시작한다.
이 때 필요한 것이 도메인 중심으로 데이터를 정리하는 것이다.
즉, 화면을 그리는 뷰 컴포넌트와 데이터를 처리하는 비즈니스 로직을 분리하는 것이다.
root에
entities
계층이 새로 추가된다.entities
안에는 도메인을 중심으로 폴더를 분리한다.테오님 포스팅에는 없지만 좀 더 이해를 돕자면 예를 들어
User
라는 도메인에 대해 entities 폴더에서 정리한다면 User에 대한 interface를 정의하고 전역 상태 관련된 로직을 정리하고 API 요청, 유틸리티 함수들, 상수를 정리할 수 있을 것이다. -
릴리즈 이후
entities에 있던 각 도메인의 정보들을 이용하는 컴포넌트들은 아직 각 pages에 속해있을 것이다. 여기서는 각 feature 하나 하나를 분리해서
api
와ui
,model
을 정리한다.즉, UI 컴포넌트와 상태관리 로직, API 호출등을 한데 모아 하나의 기능을 만들고 완전히 독립되어 유지보수, 확장성에 용이하게 만드는 것이다.
실제로 지원서 막바지 개발을 하면서 새로운 기능이 추가되거나 기존 기능을 변경해야 되는 경우들이 많아졌는데 딱 지금 타이밍을 말하는 것이구나를 느꼈다. 실제로 이렇게 분리하면 안전하게 기능을 관리하고 변경할 수 있을 것으로 기대할 수 있을 것 같다.
entities와 features
정리하다보니 entities와 features의 구분이 모호해지는 감이 있어서 이해를 돕기 위해 좀 더 정리를 해봤다.
entities 계층에는 다음과 같은 것들이 들어간다.
- 순수한 데이터 모델(기본적인 데이터 구조, 인터페이스)
- 특정 엔티티와 연관된 기본적인 도메인 비즈니스 로직
- 엔티티와 관련된 범용적인 재사용 가능한 함수(유틸리티)
- 다른 계층에 의존하지 않는 가장 기본적인 단위
feature 계층에는 사용자 인터페이스와 직접적으로 연관된 기능적 단위들이 들어간다.
- 기능과 UI 컴포넌트를 포함한다.
- 기능에 필요한 로컬 상태를 관리한다.
- 서버와 통신 로직을 포함한다.
- 여러 엔티티를 조합해서 더 복잡한 기능을 구현한다.
entities와 features와의 관계를 정리를 해보면 다음과 같이 정리할 수 있을 것 같다.
- entities는 더 낮은 추상화 수준이고, features는 더 높은 수준의 추상화를 다룬다.
- entities는 전체 애플리케이션에서 광범위하게 재사용되고, features는 특정 기능에 특화된다.
- entities는 다른 계층에 의존하지 않고, features는 entities를 사용하여 기능을 구현한다.
- entities는 UI와 직접적인 관련이 없고, features는 직접 포함한다.
- entities는 기본적인 비즈니스 로직 규칙을 포함하고, features는 복잡한 비즈니스 로직을 구현한다.
생각
처음에 개발을 시작할 때는 너무 복잡한 프로젝트 구조를 설계하는 것에 대해 오히려 복잡도를 높이고, 생산성을 떨어트리는 것 같다고 생각했다.
아직도 주니어지만 어느정도 개발하다보니 확실히 소프트웨어 공학에서 말하는 단일 책임 원칙
과 응집도는 높게, 결합도는 낮게 개발하는 것이 왜 필요한지, 지키기 위해서는 어떻게 해야되는지를 경험으로 이해하다보니 어느정도 기준이 만들어지는 것 같다.
현재 내가 생각하는 기준은 테오님이 말씀해주신 것과 완전히 공감하는 바다. 시작부터 오버엔지니어링하는 것이 아니라 시작은 시작에 맞게 개발을 하다가 불편함을 느끼는 포인트에 리팩토링을 진행하는 것이다.
최근에 릴리즈 직전에 조금 큰 기능이 들어오면서 서비스의 많은 부분에 영향도가 있는 이슈가 있었다. 이 때 FSD구조에서 지향하는 features 계층처럼 완전히 독립적으로 분리하지는 못했지만 비슷하게 적용해서 가독성도 좋고 유지보수성과 재활용성을 챙기는 리팩토링을 진행했다.
고객과 약속한 개발 일정 또한 중요하기 때문에 개발 막바지에 업무 시간 외에 별도로 야근과 주말을 포함하여 4~5일간 리팩토링을 진행했고, 꽤 만족스러운 리팩토링 과정을 경험했다.
처음에 개발을 시작할 때는 대규모 리팩토링이 두려웠지만 하면 할수록 ‘해야만 하는’타이밍이 어떤 타이밍인지 익히게 되는 것 같다. 그 타이밍을 여러번 방치하고 놓치다보면 현재 유지보수 하고 있는 서비스처럼 몇 천줄짜리 스파게티 소스들이 혼재되어 있는 모습을 보게 될 것이다.